<?php
Class TcpConnection {
    private $conn, $base;

    public function __destruct() {
        $this->conn->free();
    }

    public function __construct($base, $fd) {
        $this->base = $base;

        $this->conn = new EventBufferEvent($base, $fd, EventBufferEvent::OPT_CLOSE_ON_FREE);

        $this->conn->setCallbacks(array($this, "echoReadCallback"), NULL,
            array($this, "echoEventCallback"), NULL);

        if (!$this->conn->enable(Event::READ)) {
            echo "Failed to enable READ\n";
            return;
        }
    }

    public function echoReadCallback($bev, $ctx) {
        $realPath = 'server';
        // Copy all the data from the input buffer to the output buffer
        $input = $bev->getInput();
        $output = $bev->getOutput();
        $buf = $this->Decode($input);
        $classRet = preg_match('/Rpc-Class:\s(.*);/i', $buf, $class);
        $methodRet = preg_match('/Rpc-Method:\s(.*);/i', $buf, $method);
        $paramsRet = preg_match('/Rpc-Params:\s(.*);/i', $buf, $params);

        if($classRet && $methodRet) {
            $class = ucfirst($class[1]);
            $file = $realPath . '/' . $class . '.php';
            //判断文件是否存在，如果有，则引入文件
            if(file_exists($file)) {
                require_once $file;
                //实例化类，并调用客户端指定的方法
                $obj = new $class();
                //如果有参数，则传入指定参数
                if(!$paramsRet) {
                    $data = $obj->$method[1]();
                } else {
                    $data = $obj->$method[1](json_decode($params[1], true));
                }
                //把运行后的结果返回给客户端
                $output->add($this->Enocde($data));
            } else {
                $output->add($this->Enocde("class or method error"));
            }
        } else {
            $output->add($this->Enocde("class or method error"));
        }
        // Variant #1
        //$bev->output->addBuffer($bev->input);

        /* Variant #2 */
        /*
        $input    = $bev->getInput();
        output = $bev->getOutput();
        $output->addBuffer($input);
        */
    }

    public function Enocde($data) {
        $header =pack("NN",0XFE,strlen($data));
        $header.=$data;
        return $header;
    }

    public function Decode($input) {
        $header =$input->read(8);

        $data = unpack("Ntype/Nlen",$header);

        extract($data);
        if($type == 0xFE) {
            $size = $len;
        }
        $data = $input->read($len);
        return $data;
    }

    public function echoEventCallback($bev, $events, $ctx) {
        if ($events & EventBufferEvent::ERROR) {
            echo "Error from bufferevent\n";
        }

        if ($events & (EventBufferEvent::EOF | EventBufferEvent::ERROR)) {
            //$bev->free();
            $this->__destruct();
        }
    }
}
class Server {
    public $port;
    public $address;
    private $base;

    public function destory($server) {

        echo date('Y-m-d').'rpc server stop.....';
        foreach ($this->conn as &$c) $c = NULL;
        $this->base->free();
        $this->base->exit();
    }

    public function __destruct() {
        
    }

    public function __construct($port)
    {
        $this->port=$port;
        $this->conn = array();
    }

    public function init() {
        register_shutdown_function(array($this,'destory'),$this);
        $this->base = new EventBase();

        if(!$this->base) {
            echo "create event failed";exit;
        }

        $this->server = new EventListener($this->base,array($this,'accept'),$this->base,EventListener::OPT_CLOSE_ON_FREE | EventListener::OPT_REUSEABLE, -1,
            "0.0.0.0:{$this->port}");
        if (!$this->server) {
            echo "create server failed";
            exit(1);
        }
        $this->server->setErrorCallback(array($this, "accept_error"));
    }

    public function accept($listener, $fd, $address, $ctx) {
        $base = $this->base;
        $this->conn[] = new TcpConnection($base, $fd);
    }

    public function accept_error() {

    }

    public function dispatch() {
        echo date('Y-m-d H:i')." rpc server start.....\n";
        $this->base->dispatch();
    }

}

$server = new Server(8989);
$server->init();
$server->dispatch();
echo date('Y-m-d').'rpc server stop.....';